# REST framework之分页和视图组件

# 分页

1、看第n页,每页显示n条数据
2、在n个位置,向后查看n条数据
3、加密分页,上一页和下一页
  • # 简单的返回一张单表
    from rest_framework import serializers
    from api import models
    class PagerSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Role
            fields = '__all__'
    
    class Pager1View(APIView):
        def get(self,request,*args,**kwargs):
            roles = models.Role.objects.all()
            ser = PagerSerializer(instance=roles, many=True)
            ret = json.dumps(ser.data)
            return HttpResponse(ret) 
    
  • **初步使用渲染器:**之前的数据我们在postman中查看比较麻烦,并且还有json序列化,我们可以初步使用下restframework中的渲染器

    1、先注册rest_framework
        INSTALLED_APPS = [
    		...
            'rest_framework',
        ]
    
    2、视图函数中导入Request类,并使用
    from rest_framework import serializers
    from api import models
    from rest_framework.response import Response
    class PagerSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Role
            fields = '__all__'
    
    class Pager1View(APIView):
        def get(self,request,*args,**kwargs):
            roles = models.Role.objects.all()
            ser = PagerSerializer(instance=roles, many=True)
            return Response(ser.data)   
        
      # url中
    	 url(r'(?P<version>[v1|v2]+)/pager1/$',views.Pager1View.as_view()),
    
    • # 小提示:
      通过上面的渲染,当我们打开浏览器输入网址后,就会出现restframework渲染后的效果
      

# 第一种分页:看第n页,每页显示n条数据

# 使用分页类进行分页
from rest_framework import serializers
from api import models
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination
class PagerSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Role
        fields = '__all__'

class Pager1View(APIView):
    def get(self,request,*args,**kwargs):
        # 获取所有数据
        roles = models.Role.objects.all()
        # 创建分页对象
        pg = PageNumberPagination()
        # 在数据库中获取分页数据
        page_role = pg.paginate_queryset(queryset=roles,request=request,view=self) # 在全局配置PAGE_SIAE
        print(page_role)  # [<Role: Role object>, <Role: Role object>, <Role: Role object>]
        # 对数据进行序列化
        ser = PagerSerializer(instance=page_role, many=True)
        return Response(ser.data)
  • # 配置settings.py文件
    REST_FRAMEWORK = {
        'DEFAULT_VERSION':'v1',
        'ALLOWED_VERSIONS':['v1','v2'],
        'VERSION_PARAM':'version',  # 允许得key;version=v1
        'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.URLPathVersioning',
        'DEFAULT_PARSER_CLASSES':['rest_framework.parsers.JSONParser',
        							'rest_framework.parsers.FormParser'],
        'PAGE_SIZE':3,  # 必须配置PAGE_SIZE,否则取出的为None
    }
    
# 自定义分页类
class MyPageNumberPagination(PageNumberPagination):
    
    page_size = 2  # 每页显示个数
    page_size_query_param = 'size'  #配置在url可以接受size的大小的参数:/api/v1/pager1/?page=2&size=4,第二页4条数据 
    max_page_size = 5 # 表示最大一页的条数,如果size过大对数据库有影响
    page_query_param = 'page' # 获取页面的参数
    

class Pager1View(APIView):
    def get(self,request,*args,**kwargs):
        # 获取所有数据
        roles = models.Role.objects.all()
        # 创建分页对象
        pg = MyPageNumberPagination()
        # 在数据库中获取分页数据
        page_role = pg.paginate_queryset(queryset=roles,request=request,view=self) # 在全局配置PAGE_SIAE
        print(page_role)  # [<Role: Role object>, <Role: Role object>, <Role: Role object>]
        # 对数据进行序列化
        ser = PagerSerializer(instance=page_role, many=True)
        
        # return Response(ser.data)
        return pg.get_paginated_response(ser.data) # 返回的数据中带有上一页和下一页的网址
    

# 第二种分页:在n个位置,向后查看n条数据

# 自定义偏移分页类
from rest_framework.pagination import LimitOffsetPagination
class MyLimitOffsetPagination(LimitOffsetPagination):
    default_limit = 2
    limit_query_param = 'limit'  # 限制的参数
    offset_query_param = 'offset'  #偏移的参数
    
    max_limit = 5 # /api/v1/pager1/?limit=4&offset=5, 从第4开始取4条,5去不到
    
class Pager1View(APIView):
    def get(self,request,*args,**kwargs):
        # 获取所有数据
        roles = models.Role.objects.all()
        # 创建分页对象
        pg = MyLimitOffsetPagination()
        # 在数据库中获取分页数据
        page_role = pg.paginate_queryset(queryset=roles,request=request,view=self) # 在全局配置PAGE_SIAE
        print(page_role) 
        # 对数据进行序列化
        ser = PagerSerializer(instance=page_role, many=True)
        
        # return Response(ser.data)
        return pg.get_paginated_response(ser.data)

# 第三种分页:加密分页,只有上一页和下一页

  • 通过自己的加密规则,你以为下一页是?couser=2吗?答案是不是的!

    from rest_framework.pagination import CursorPagination
    class MyCursorPagination(CursorPagination):
        cursor_query_param = 'cursor'  # url参数
        page_size = 2
        invalid_cursor_message = ('无效游标')
        ordering = 'id' #  排序
        page_size_query_param = 'size'  # 定制每页大小的参数
    
        max_page_size = 5 #/api/v1/pager1/?cursor=cj0xJnA9Mw%3D%3D&limit=4&offset=5
    
    class Pager1View(APIView):
        def get(self,request,*args,**kwargs):
            # 获取所有数据
            roles = models.Role.objects.all()
            # 创建分页对象
            pg = MyCursorPagination()
            # 在数据库中获取分页数据
            page_role = pg.paginate_queryset(queryset=roles,request=request,view=self) # 在全局配置PAGE_SIAE
            print(page_role)  # [<Role: Role object>, <Role: Role object>, <Role: Role object>]
            # 对数据进行序列化
            ser = PagerSerializer(instance=page_role, many=True)
            
            # return Response(ser.data)
            return pg.get_paginated_response(ser.data)
    
# 总结
1、如果数据量比较大,如何做分页?
	答:通过记录每个分页的最大值和最小值,下一页在最大值基础上偏移多少个数据;但是一般的页码都是通过有规律的数字进行传递,那么存在输入过大页码,所以我们可以对url传递页码的参数进行加密,让用户不知道下一页的页码号

# 视图

  • 简单回顾我们学过的视图类

    1、最初版本:
    	View
    	
    2、rest_framework中:
    	APIView --> 继承View
    	
    3、rest_framework中的更深层视图
    	GenericAPIView(更大层度的封装了方法) --> 继承APIView
    	
    4、rest_framework.viewsets的GenericViewSet --> 继承ViewSetMixin,GenericAPIView
    	GenericAPIView结合ListModelMixin、CreateModelMixin...用来创建、查看等方法
    
    5、rest_framework.viewsets的ModelViewSet --> 继承
    
    	class ModelViewSet(mixins.CreateModelMixin, # 增
                       mixins.RetrieveModelMixin,  #获取,需要id
                       mixins.UpdateModelMixin,  #更新,需要id
                       mixins.DestroyModelMixin, #删除,需要id
                       mixins.ListModelMixin, # 列出
                       GenericViewSet)
        
        # 针对CreateModelMixin、ListModelMixin使用的url路径不需要传递参数:
        	url(r'(?P<version>[v1|v2]+)/v1/$',views.V1View.as_view({'get':'list',
                                                                    'post':'create'})),
        # 针对RetrieveModelMixin、UpdateModelMixin、DestroyModelMixin因为需要传递pk所以,url:
        	url(r'(?P<version>[v1|v2]+)/v1/(?P<pk>\d+)/$',views.V1View.as_view({
                									 'get':'retrieve',
                                                     'delete':'destroy',
                                                     'put':'update',
                									 'patch':'partial_update',
           									 })),                		    	
    
  • # GenericAPIView视图类的使用
    from rest_framework.response import Response
    from rest_framework.generics import GenericAPIView
    
    class PagerSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Role
            fields = '__all__'
    
    class V1View(GenericAPIView):
        queryset = models.Role.objects.all()  # 最先获得QuerySet数据
        serializer_class = PagerSerializer    # 再获得序列化类
        pagination_class = PageNumberPagination  # 最后获取到分页类
        def get(self,request,*args,**kwargs):
            roles= self.get_queryset()  # 自动取上面配置好的queryset
            pager_roles = self.paginate_queryset(roles) # 找我们写的分页类,然后自动传参进入,返回分页数据
            ser = self.get_serializer(instance=pager_roles,many=True) # 序列化
            return Response(ser.data)
    
# GenericViewSet讲解
	正如它的名字一样,它只完成一个功能:就是对as_view() 函数进行重写了,里面需要传入一个字典记录不同请求方式所对应的CBV中的触发的视图函数
  • # 简单实例
    from rest_framework.response import Response
    from rest_framework.viewsets import GenericViewSet
    
    class PagerSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Role
            fields = '__all__'
    
    class V1View(GenericViewSet):
        queryset = models.Role.objects.all()  # 最先获得QuerySet数据
        serializer_class = PagerSerializer    # 再获得序列化类
        pagination_class = PageNumberPagination  # 最后获取到分页类
        def xxx(self,request,*args,**kwargs):
            roles= self.get_queryset()  # 自动取上面配置好的queryset
            pager_roles = self.paginate_queryset(roles) # 找我们写的分页类,然后自动传参进入,返回分页数据
            ser = self.get_serializer(instance=pager_roles,many=True) # 序列化
            return Response(ser.data)
     
     # 对应的url中
    urlpatterns = [
     	...
        url(r'(?P<version>[v1|v2]+)/v1/$',views.V1View.as_view({'get':'xxx',})),
    ] 
    
  • # GenericAPIView结合ListModelMixin、CreateModelMixin...用来创建、查看等方法
      # views.py文件中
    from rest_framework.viewsets import GenericViewSet
    from rest_framework.mixins import ListModelMixin,CreateModelMixin
    
    class PagerSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Role
            fields = '__all__'
    
    class V1View(ListModelMixin,CreateModelMixin,GenericViewSet):
        queryset = models.Role.objects.all()  # 最先获得QuerySet数据
        serializer_class = PagerSerializer    # 再获得序列化类
        pagination_class = PageNumberPagination  # 最后获取到分页类
     
     
     # 重点:url.py文件中
    urlpatterns = [
       ...
        url(r'(?P<version>[v1|v2]+)/v1/$',views.V1View.as_view({'get':'list','post':'create'})), # 触发list函数,再ListModelMixin中的,根据不同的请方式实现不同的方法...
    ]
    
  • # 最牛逼的ModelViewSet类的使用
     #url.py文件中
    
    urlpatterns = [
    	...
        url(r'(?P<version>[v1|v2]+)/v1/$', views.V1View.as_view({'get': 'list', 'post': 'create'})),
        url(r'(?P<version>[v1|v2]+)/v1/(?P<pk>\d+)/$', views.V1View.as_view({
            'get': 'retrieve',
            'delete': 'destroy',
            'put': 'update',
            'patch': 'partial_update',
        })),
    ]
    
     # views.py文件中
    from rest_framework.viewsets import GenericViewSet,ModelViewSet
    class PagerSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Role
            fields = '__all__'
    
    class V1View(ModelViewSet):
        queryset = models.Role.objects.all()  # 最先获得QuerySet数据
        serializer_class = PagerSerializer    # 再获得序列化类
        pagination_class = PageNumberPagination  # 最后获取到分页类 
    
    • 小提示:

      1、以上代码我们就可以实现增删改查了,就只有四行代码
      2、总结:
      	a. 增删改查 ModelViews
      	b. 增删: CreateModelMixin、DestroyModelMixin
      	c. 复杂的逻辑: GenericViewSet、APIView
      

# 使用Serializer类来实现增删改查

  • models.py文件中

    from django.db import models
    
    class Publisher(models.Model):
        name = models.CharField(max_length=32)
    
    class Author(models.Model):
        name = models.CharField(max_length=32, )
    
    class Book(models.Model):
        title = models.CharField(max_length=32)
        price = models.DecimalField(max_digits=6, decimal_places=2)
        pub_date = models.DateTimeField(default='2019-01-01 10:10:10')
        pub = models.ForeignKey('Publisher', on_delete=models.CASCADE)
        authors = models.ManyToManyField('Author')
    
    
  • serializer.py文件中

    from rest_framework import serializers
    from app01 import models
    
    class PublisherSerializer(serializers.Serializer): # 序列化Publisher表
        name = serializers.CharField()
        
    class AuthorSerializer(serializers.Serializer): # 序列化Auhor表
        id = serializers.IntegerField()
        name = serializers.CharField()
    
    class BookSerializer(serializers.Serializer):  # 序列化Book表
        title = serializers.CharField()
        price = serializers.DecimalField(max_digits=6, decimal_places=2)
        pub_date = serializers.DateTimeField()
    
        pub = PublisherSerializer(required=False, read_only=True)  # 表示此字段再post情况下非必填,且自读
        authors = serializers.SerializerMethodField(read_only=True)  # get_字段名,自读自定义函数
    
        post_pub = serializers.IntegerField(write_only=True) # 只写
        post_author = serializers.ListField(write_only=True) # 只写列表
    
        def get_authors(self, obj):
            ser_obj = AuthorSerializer(obj.authors.all(), many=True)
            return ser_obj.data
    
        def create(self, validated_data):
            book_obj = models.Book.objects.create(
                title=validated_data['title'],
                price=validated_data['price'],
                pub_date=validated_data['pub_date'],
                pub_id=validated_data['post_pub']
            )
            book_obj.authors.set(validated_data['post_author'])
            return book_obj
    
        def update(self, instance, validated_data):
            instance.title = validated_data.get('title',instance.title)
            instance.price = validated_data.get('price',instance.price)
            instance.pub_date = validated_data.get('pub_date',instance.pub_date)
            instance.pub_id = validated_data.get('post_pub',instance.pub_id)
            instance.save()
            instance.authors.set(validated_data.get('post_authors',instance.authors.all()))
            return instance
    
    
  • 对应的views.py

    from django.core import serializers
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from app01.serializer import BookSerializer
    class BookListView(APIView):
        def get(self, request, *args, **kwargs):  # 显示和新建一个url,因为不用pk
            all_books = models.Book.objects.all()
            ser_obj = BookSerializer(all_books, many=True)
    
            return Response(ser_obj.data)
    
        def post(self, request, *args, **kwargs):
            ser_obj = BookSerializer(data=request.data)
            if ser_obj.is_valid():
                ser_obj.save()
                return Response(ser_obj.data)
            return Response(ser_obj.errors)
    
    
    class BookView(APIView):  # 获取单条数据/更新/删除得另一个url
        def get(self, request, pk, *args, **kwargs):
            book_obj = models.Book.objects.filter(pk=pk).first()
            ser_obj = BookSerializer(book_obj)
    
            return Response(ser_obj.data)
    
        def put(self, request, pk, *args, **kwargs):
            book_obj = models.Book.objects.filter(pk=pk).first()
            ser_obj = BookSerializer(book_obj, data=request.data, partial=True)
            if ser_obj.is_valid():
                ser_obj.save()
                return Response(ser_obj.data)
            return Response(ser_obj.errors)
    
        def delete(self, request, pk, *args, **kwargs):
            obj = models.Book.objects.filter(pk=pk).first()
            if obj:
                obj.delete()
                return Response({'msg': '删除成功'})
            return Response({'error': '数据不存在'})
    
    
  • url.py文件

    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^books/', views.BookListView.as_view()),
        url(r'^book/(\d+)/', views.BookView.as_view()),
    ]